/*
    poparser  v0.91.0 Copyright (C) 2023  Zhai YongNing <zhaiyn-free@foxmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
   <https://www.gnu.org/licenses/gpl-3.0.txt>
*/
#ifdef NDEBUG
#undef POP_DEBUG
#else
#define POP_DEBUG 0 
#endif

#include "poparser.h"
#include <cstdlib>
#include <cstring>
#ifdef POP_DEBUG
#include <ctype.h>
#include <iomanip>
#endif

#ifdef POP_DEBUG
#define POP_DEBUG_BEGIN_MESSAGE                                               \
  {                                                                           \
    std::cerr << "Debug-" << POP_DEBUG << " level BEGIN >>" << std::endl;     \
  }
#define POP_DEBUG_END_MESSAGE                                                 \
  {                                                                           \
    std::cerr << "<< Debug-" << POP_DEBUG << " level END" << std::endl;       \
  }
#else
#define POP_DEBUG_BEGIN_MESSAGE                                               \
  {                                                                           \
  }
#define POP_DEBUG_END_MESSAGE                                                 \
  {                                                                           \
  }
#endif

struct visible_char
{
  unsigned char vc;
  visible_char (char c = (unsigned char)0) { vc = (unsigned char)c; }
};
static std::ostream &
operator<< (std::ostream &os, visible_char c)
{
  if (isprint (c.vc))
    {
      os << "'" << c.vc << "'   ";
    }
  else
    {
      os << "'\\x" << std::hex << (unsigned int)(c.vc) << std::dec << "'";
    }
  return os;
}

POParser::POParser ()
{
  optargs.clear ();
  options_map.clear ();
  long_options = nullptr;
  short_options.clear ();
}

void
POParser::__copy (const POParser &p)
{
  parser_be_init=p.parser_be_init;
  optargs = p.optargs;
  options_map = p.options_map;
  if (long_options != nullptr)
    {
      delete[] long_options;
      long_options = nullptr;
    }
  if (p.long_options != nullptr)
    {
      int c, i;
      for (c = 0;; c++)
        if (p.long_options[c].val == 0 && p.long_options[c].name == nullptr
            && p.long_options[c].flag == nullptr
            && p.long_options[c].has_arg == 0)
          break;
      if ((long_options = new struct option[c + 1]) == nullptr)
        {
          ::abort ();
        };
      ::memcpy (long_options, p.long_options,
                sizeof (struct ::option) * (c + 1));
    }
  short_options = p.short_options;
}

POParser::POParser (const POParser &p) { __copy (p); }
POParser &
POParser::operator= (const POParser &p)
{
  if (this != &p)
    __copy (p);
  return *this;
}
void
POParser::__move (POParser &&p)
{
  parser_be_init=p.parser_be_init;
  p.parser_be_init=false;
  optargs = std::move (p.optargs);
  options_map = std::move (p.options_map);
  if (long_options != nullptr)
    {
      delete[] long_options;
      long_options = nullptr;
    }
  long_options = p.long_options;
  p.long_options = nullptr;
  short_options = std::move (p.short_options);
};
POParser::POParser (POParser &&p) { __move (std::move (p)); }
POParser &
POParser::operator= (POParser &&p)
{
  if (this != &p)
    __move (std::move (p));
  return *this;
}

POParser::POParser (
    const std::initializer_list<
        const std::tuple<const char *, const std::vector<const char *>,
                         const NumOfArg, const bool, int *const, const int,
                         bool (*) (std::vector<std::string> &)> > &ilist)
{

  optargs.clear ();
  options_map.clear ();
  size_t struct_option_number = 0;
  for (auto &o : ilist)
    struct_option_number += std::get<1> (o).size ();
  long_options = new struct option[struct_option_number + 1];
  size_t long_option_counter = 0;
  for (const auto &o : ilist)
    {
      const auto &i_so = std::get<0> (o);
      const auto &i_los = std::get<1> (o);
      const auto &i_numofarg = std::get<2> (o);
      const auto &i_more_arg = std::get<3> (o);
      const auto &i_variable = std::get<4> (o);
      const auto &i_value = std::get<5> (o);
      const auto &i_func = std::get<6> (o);
#if POP_DEBUG > 5
      POP_DEBUG_BEGIN_MESSAGE;
      std::cerr << visible_char (*i_so);
      std::cerr << "{";
      for (auto s = i_los.begin (); s < i_los.end (); ++s)
        {
          std::cerr << "\"" << *s << "\"";
          if (s + 1 != i_los.end ())
            std::cerr << ",";
        }
      std::cerr << "}";
      switch (i_numofarg)
        {
        case NumOfArg::No:
          std::cerr << "No";
          break;
        case NumOfArg::Optional:
          std::cerr << "Optionl";
          break;
        case NumOfArg::Has:
          std::cerr << "Has";
          break;
        default:
          break;
        }
      std::cerr << (i_more_arg ? " more " : " no_more") << i_variable << " "
                << i_value << " " << i_func << std::endl;
      POP_DEBUG_END_MESSAGE;
#endif
#ifndef NDEBUG
      if (!(isprint (*i_so) || *i_so == 0) || *i_so == '?' || *i_so == ':'
          || *i_so == ' ' || *i_so == '-')
        {
          std::cerr << "ERROR:"
                    << "short-option " << visible_char (*i_so) << "is error!"
                    << std::endl;
          ::abort ();
        }
#endif
      bool has_valid_long_option = false;
      for (auto &lo : i_los)
        {
          if (*lo == 0)
            break;
          has_valid_long_option = true;
          long_options[long_option_counter].name = lo;
          long_options[long_option_counter].has_arg
              = i_numofarg == NumOfArg::Has
                    ? required_argument
                    : (i_numofarg == NumOfArg::Optional ? optional_argument
                                                        : no_argument);
          long_options[long_option_counter].flag = (int *)nullptr;
          long_options[long_option_counter].val = *i_so;
#ifndef NDEBUG
          for (size_t i = 0; i < long_option_counter; i++)
            if (::strcmp (lo, long_options[i].name) == 0)
              {
                std::cerr << "ERROR:"
                          << " long-option \"" << lo << "\"is repeated!"
                          << std::endl;
                ::abort ();
              }
#endif
          if (!(*i_so))
            {
              auto &om = options_map[std::string ("--") + lo];
              auto &om_need_more_arg = std::get<0> (om);
              auto &om_optional_arg = std::get<1> (om);
              auto &om_variable = std::get<2> (om);
              auto &om_value = std::get<3> (om);
              auto &om_func = std::get<4> (om);
              om_need_more_arg
                  = (i_numofarg != NumOfArg::No) ? i_more_arg : false;
              om_optional_arg = (i_numofarg == NumOfArg::Optional);
              om_variable = i_variable;
              om_value = i_value;
              om_func = i_func;
            }
          long_option_counter++;
        }
#ifndef NDEBUG
      if (*i_so == 0 && !has_valid_long_option)
        {
          std::cerr << "ERROR:"
                    << "both short-option and long-option are null !"
                    << std::endl;
          ::abort ();
        }
      if (short_options.find (*i_so) != std::string::npos)
        {
          std::cerr << "ERROR:"
                    << "short-option " << visible_char (*i_so)
                    << "is repeated!" << std::endl;
          ::abort ();
        }
#endif

      if (*i_so)
        {
          short_options += *i_so;
          if (i_numofarg != NumOfArg::No)
            {
              short_options += ':';
              if (i_numofarg == NumOfArg::Optional)
                short_options += ':';
            }
          auto &om = options_map[std::string ("-") + *i_so];
          auto &om_need_more_arg = std::get<0> (om);
          auto &om_optional_arg = std::get<1> (om);
          auto &om_variable = std::get<2> (om);
          auto &om_value = std::get<3> (om);
          auto &om_func = std::get<4> (om);
          om_need_more_arg = (i_numofarg != NumOfArg::No) ? i_more_arg : false;
          om_optional_arg = (i_numofarg == NumOfArg::Optional);
          om_variable = i_variable;
          om_value = i_value;
          om_func = i_func;
        }
      long_options[long_option_counter].name = nullptr;
      long_options[long_option_counter].has_arg = 0;
      long_options[long_option_counter].flag = nullptr;
      long_options[long_option_counter].val = 0;
    }
#if POP_DEBUG > 4
  {
    POP_DEBUG_BEGIN_MESSAGE;
    static const char *has_message[] = { "0_arg", "1_arg", "01_arg" };
    std::cerr << "short_options: '" << short_options << '\'' << std::endl;
    std::cerr << "long_options" << std::endl;
    std::cerr << std::setw (20) << "name" << std::setw (10) << "has_arg"
              << std::setw (10) << "flag" << std::setw (10) << "val"
              << std::endl;
    for (int i = 0; long_options[i].name != nullptr; i++)
      {
        std::cerr << std::setw (20) << long_options[i].name;
        std::cerr << std::setw (10) << has_message[long_options[i].has_arg]
                  << ':' << long_options[i].has_arg;
        std::cerr << std::setw (10) << long_options[i].flag;
        unsigned char tmp = long_options[i].val;
        std::cerr << std::setw (8) << '\'';
        if (isprint (tmp))
          std::cerr << tmp;
        else
          std::cerr << "\\x" << std::hex << (unsigned int)(tmp);
        std::cerr << '\'' << std::dec << std::endl;
      }
    std::cerr << "Need more arg:";
    for (auto &i : options_map)
      {
        if (std::get<0> (std::get<1> (i)))
          std::cerr << std::get<0> (i) << ' ';
      }
    std::cerr << std::endl;

    POP_DEBUG_END_MESSAGE;
  }
#endif

}
POParser::~POParser ()
{
  if (long_options != nullptr)
    delete[] long_options;
}
bool
POParser::parse (const int argc, char **argv, bool append)
{
  if (!append)
    optargs.clear ();
  opterr = 0;
  optind = 1;
  while (true)
    {
      int option_index = 0;
      char c = getopt_long (argc, argv, short_options.c_str (), long_options,
                            &option_index);
      if (c == -1)
        {
#if POP_DEBUG > 4
          std::cerr << "opt parse is finish!" << std::endl;
#endif
          break;
        }
      if (c == '?')
        {

          std::cerr << "unknown option or a argument be needed: ";
          if (optopt)
            std::cerr << '-' << visible_char (c);
          else
            std::cerr << argv[optind - 1];
          std::cerr << std::endl;
          ::abort ();
          break;
        }
      auto &va
          = optargs[c ? std::string ("-") + char (c)
                      : std::string ("--") + long_options[option_index].name];
      auto &om = options_map[c ? std::string ("-") + char (c)
                               : std::string ("--")
                                     + long_options[option_index].name];
      auto &om_need_more_arg = std::get<0> (om);
      auto &om_optional_arg = std::get<1> (om);
      auto &om_variable = std::get<2> (om);
      auto &om_value = std::get<3> (om);
      auto &om_func = std::get<4> (om);
      if (optarg)
        {
          if (!va.empty ())
            {
              if (!va[0].empty ())
                va.insert (va.begin (), optarg);
              else
                va[0] = optarg;
            }
          else
            va.push_back (optarg);
          while (om_need_more_arg && optind < argc && *argv[optind] != '-')
            {
              // va.push_back (argv[optind]);
              va.insert (va.begin (), argv[optind]);
              ++optind;
            }
        }
      else
        {
          if (va.empty ())
            va.push_back (std::string ());
          if (om_optional_arg && optind < argc && *argv[optind] != '-')
            {
              do
                {
                  if (va[0].empty ())
                    va[0] = argv[optind];
                  else
                    // va.push_back (argv[optind]);
                    va.insert (va.begin (), argv[optind]);
                  ++optind;
                }
              while (om_need_more_arg && optind < argc
                     && *argv[optind] != '-');
            }
        }
      if (om_variable)
        *om_variable = om_value;
      if (om_func)
        if (!om_func (va))
          ::abort ();
    }
  while (optind < argc)
    {
      auto &va = optargs["--"];
      // va.push_back (argv[optind]);
      va.insert (va.begin (), argv[optind]);
      ++optind;
    }
#if POP_DEBUG > 4
  {
    std::cerr << "optargs" << std::endl;
    for (auto &oa : optargs)
      {
        auto &o = std::get<0> (oa);
        auto &a = std::get<1> (oa);
        std::cerr << o << ':';
        for (auto &s : a)
          {
            std::cerr << s << ':';
          }
        std::cerr << std::endl;
      }
  }
#endif
  parser_be_init=true;
  return true;
}
POParser::operator bool () const
{
  for (auto o : optargs)
    if (!std::get<1> (o).empty ())
      return true;
  return false;
}
const decltype (POParser::optargs)::mapped_type &POParser::operator[] (
    const std::string c) const
{
  auto a = optargs.find (c);
  if (a == optargs.cend ())
    return null_v;
  return std::get<1> (*a);
}
const decltype (POParser::optargs)::mapped_type &POParser::append (
    const std::string c, const std::string &arg)
{
  auto im = optargs.find (c);
  if (im == optargs.end ())
    {
      optargs[c].push_back (arg);
    }
  else
    {
      if (im->second[0].empty ())
        im->second[0] = arg;
      else if (!arg.empty ())
        im->second.insert (im->second.begin (), arg);
    }
  return optargs.find (c)->second;
};
bool
POParser::remove (const std::string c)
{
  auto im = optargs.find (c);
  if (im != optargs.end ())
    {
      optargs.erase (im);
      return true;
    }
  return false;
};

std::ostream &
operator<< (std::ostream &os, const POParser &p)
{
  for (auto &oa : p.optargs)
    {
      os << "{ \"" << std::get<0> (oa) << "\",";
      os << " {";
      for (auto it = std::get<1> (oa).begin ();;)
        {
          os << " \"" << *it << '"';
          ++it;
          if (it != std::get<1> (oa).end ())
            os << ',';
          else
            break;
        }
      os << " } };" << std::endl;
    }
  return os;
};